<!--
购物车，类似taobao那样的购物车功能，可以跨页选择条目，把条目加入到购物车中，最后弹出一个对话框，执行“结算”

参数：
btnTitle - 按钮标题
dlgTitle - 对话框标题
tableHeight - 滚动区域高度，默认370px
serverErrorMessage - (v-model)服务器错误信息，如果不为空，则呈现banner
workingState - true表示繁忙，确认按钮会显示活动指示器
disable - 按钮的禁用状态
disableWhenEmpty - 如果购物车为空，则禁用按钮
qtyReadonly - 数量字段是否可以编辑

idFieldName - "ID"字段的名称，默认就是'id'
qtyFieldName - "数量"字段的名称，默认就是'qty'

validateLineQty - 验证明细行的qty的方法(可选) (value: string, lineIdx: number) => string，返回非空字符串表示的是出错信息，正常返回空字符串
validateSlot - 插槽表单的验证方法(可选) () => Promise<boolean>

columnsInfo - 要展示的列定义, 跟q-table要求的columns相对应, 例如:
[
  {
    label: '采购单明细ID',
    name: 'orderLineId',
    field: 'orderLineId',
    sortable: true,
    align: 'left'
  },
  {
    label:'收货数量',
    name:'qty',
    field:'qty',
    sortable: false,
    align: 'left'
  },
  {
    label:'料号',
    name:'sku',
    field:'sku',
    sortable: true,
    align: 'left'
  },
  // ...
]
pendingItems - (v-model)购物车中的数据，例如:
[
  {
    orderLineId: 13402,
    qty: 1000,
    sku: 'AX210E-6',
  },
  // ...
]

事件：
'update:pending-items': 按了确定按钮，删除了一行，或清空所有
'update:server-error-message': 用户可以"x"掉这个错误信息
'before-show': 购物车对话框出现前，可以在这里做一些额外的工作
'on-confirm': 点了确认按钮，此时pending-items已经更新

方法：
closeDlg: 关闭对话框
-->
<template>
  <div>
    <q-btn color="primary" :label="$t(props.btnTitle)" @click="onShoppingCartBtnClicked"
      :disable="props.disable || (props.disableWhenEmpty && badgeCnt===0)">
      <q-badge color="red" floating v-if="badgeCnt>0">{{ badgeCnt }}</q-badge>
    </q-btn>
    <q-dialog position="top" v-model="$d.dlgState" transition-show="none" transition-hide="none"
      :persistent="props.workingState">
      <q-card class="q-pa-sm" style="max-width: 1200px; width: 100%; top:50px">
        <q-card-section class="q-pa-sm">
          <div class="text-h6">{{ $t(props.dlgTitle) }}</div>
        </q-card-section>
        <q-separator />
        <q-banner class="text-white bg-red q-mt-sm" dense inline-actions v-if="props.serverErrorMessage">
          {{ props.serverErrorMessage }}
          <template v-slot:action>
            <q-btn flat icon="close" size="sm" dense @click="emit('update:server-error-message', '')" />
          </template>
        </q-banner>
        <slot name="extra-form"></slot>
        <q-table :columns="$d.columnsInfoInternal" :rows="pendingItemsInternal" :hide-pagination="true"
          :hide-selected-banner="true" v-model:pagination="$d.paginationInfo" color="primary" dense flat
          :row-key="props.idFieldName" separator="cell" class="q-mt-sm simple-border generic-sticky-header-table"
          :style="tableStyle">
          <template v-slot:no-data>
            <span class="full-width text-center"> <q-icon name="error_outline" /> {{$t('无数据')}} </span>
          </template>
          <template v-slot:body-cell-operation="properties">
            <q-td :props="properties" auto-width>
              <q-btn class="q-mr-xs btn-in-row" color="negative" dense :label="$t('移除')"
                @click="removeItem(properties.rowIndex)"
                :disable="props.workingState" />
            </q-td>
          </template>
          <template v-slot:body-cell-qty="properties">
            <q-td :props="properties" style="width: 100px;padding:1px">
              <easy-input :error="!!validationResult[properties.rowIndex]"
                :error-message="validationResult[properties.rowIndex]"
                v-model="$d.qtyInfoInternal[properties.row[props.idFieldName]]" select-all-on-focus
                :readonly="props.workingState || props.qtyReadonly"></easy-input>
            </q-td>
          </template>
        </q-table>
        <div class="row q-gutter-xs q-pt-sm">
          <q-btn color="negative" :label="$t('清空')" @click="clearAll" :disable="props.workingState||badgeCnt===0" />
          <q-space />
          <q-btn color="primary" :label="$t('确定')" @click="onConfirm" :disable="props.workingState||badgeCnt===0"
            :loading="props.workingState" />
          <q-btn v-close-popup unelevated color="primary" :label="$t('取消')" outline />
        </div>
      </q-card>
    </q-dialog>
  </div>
</template>

<script lang="ts">
export default {
  name: 'shoppingCart'
}
</script>
<script lang="ts" setup>
import _ from 'lodash'
import {computed, reactive} from 'vue'
import {ColumnInfo} from '../defs/comm'
import {useI18n} from 'vue-i18n'

interface PropsDef {
  tableHeight?: string
  btnTitle?: string
  dlgTitle?: string
  columnsInfo?: ColumnInfo[]
  pendingItems?: Record<string, any>[]
  serverErrorMessage?: string
  workingState?: boolean
  disable?: boolean
  disableWhenEmpty?: boolean
  idFieldName?: string
  qtyFieldName?: string
  qtyReadonly?: boolean
  validateLineQty?: (value: string, lineIdx: number) => string //用于验证一行的qty是否正确，不正确返回错误提示，否则返回null
  validateSlot?: () => Promise<boolean> //用于验证slot中的表单是否正确
}

const props = withDefaults(defineProps<PropsDef>(), {
  tableHeight: '370px',
  btnTitle: '待处理条目',
  dlgTitle: '待处理条目',
  qtyReadonly: false,
  columnsInfo: () => [] as ColumnInfo[],
  pendingItems: () => [] as Record<string, any>[],
  serverErrorMessage: '',
  workingState: false,
  disable: false,
  disableWhenEmpty: false,
  idFieldName: 'id',
  qtyFieldName: 'qty'
})

let $d = reactive({
  paginationInfo: {
    // 页码
    page: 1,
    // 每页多少条，0表示不限
    rowsPerPage: 0
  },
  // 对话框显示/隐藏状态
  dlgState: false,
  // 内部qty信息，记录每一行究竟填了什么数字
  qtyInfoInternal: {} as Record<string | number, string>,
  // 列信息
  columnsInfoInternal: [] as ColumnInfo[],
})

let tableStyle = computed(() => {
  return {height: props.tableHeight}
})

const badgeCnt = computed(() => {
  return pendingItemsInternal.value.length
})

//emit定义
const emit = defineEmits<{
  (e: 'update:server-error-message', data: string): void
  (e: 'update:pending-items', data: Record<string, any>[]): void
  (e: 'on-confirm'): void
  (e: 'before-show'): void
}>()

const doConfirm = function () {
  let pendingItems = _.cloneDeep(pendingItemsInternal.value)
  _.forEach(pendingItems, (item) => {
    item[props.qtyFieldName] = $d.qtyInfoInternal[item[props.idFieldName] as string | number]
  })
  emit('update:pending-items', pendingItems)
  emit('on-confirm')
}

const onConfirm = function () {
  if (_.some(validationResult.value)) {
    return
  }
  if (props.validateSlot) {
    void props.validateSlot().then(valRes => {
      if (valRes) {
        doConfirm()
      }
    })
  } else {
    doConfirm()
  }
}

const pendingItemsInternal = computed(() => {
  return _.cloneDeep(props.pendingItems)
})

const validationResult = computed(() => {
  const result: string[] = []
  _.forEach(pendingItemsInternal.value, function (item, rowIndex) {
    if (props.validateLineQty && $d.dlgState) {
      result.push(props.validateLineQty($d.qtyInfoInternal[item[props.idFieldName] as string | number], rowIndex))
    } else {
      result.push('')
    }
  })
  return result
})

const {t} = useI18n()
const onShoppingCartBtnClicked = function () {
  emit('before-show')

  const columnsInfo = _.clone(props.columnsInfo)
  columnsInfo.unshift({
    label: t('操作'),
    name: 'operation',
    field: 'operation',
    align: 'left',
    defOrder: 0,
    defVisible: true
  })
  $d.columnsInfoInternal = columnsInfo

  // qtyInfo其实就是id=>qty，但qty为啥用string呢? 因为input绑定的内容其实本质上都是string
  let qtyInfo = {} as Record<string | number, string>
  _.forEach(pendingItemsInternal.value, (item: Record<string, any>) => {
    qtyInfo[item[props.idFieldName] as string | number] = _.toString(item[props.qtyFieldName])
  })
  $d.qtyInfoInternal = qtyInfo

  $d.dlgState = true
}

const removeItem = function (rowIndex: number) {
  let pendingItems = _.clone(pendingItemsInternal.value)
  pendingItems.splice(rowIndex, 1)
  emit('update:pending-items', pendingItems)
}

const clearAll = function () {
  emit('update:pending-items', [])
}

const closeDlg = function () {
  $d.dlgState = false
}

defineExpose({
  closeDlg
})

</script>
